package org.springblade.modules.admin.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.aliyun.oss.ServiceException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springblade.core.mp.base.BaseEntity;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringUtil;
import org.springblade.modules.admin.dto.ElevatorSaveDto;
import org.springblade.modules.admin.dto.UserTrajectoryDTO;
import org.springblade.modules.admin.dto.UserTrajectorySearchDTO;
import org.springblade.modules.admin.entity.MaintainPlanContent;
import org.springblade.modules.admin.entity.UserTrajectory;
import org.springblade.modules.admin.enums.BuildingCacheEnum;
import org.springblade.modules.admin.enums.UserTrajectoryEnum;
import org.springblade.modules.admin.mapper.UserTrajectoryMapper;
import org.springblade.modules.admin.service.BuildingService;
import org.springblade.modules.admin.service.IMaintainPlanContentService;
import org.springblade.modules.admin.service.IUserTrajectoryService;
import org.springblade.modules.admin.utils.DistanceUtils;
import org.springblade.modules.admin.vo.BuildingRedisVO;
import org.springblade.modules.admin.vo.UserTrajectoryCountVO;
import org.springblade.modules.admin.vo.UserTrajectoryQueryVO;
import org.springblade.modules.admin.vo.UserTrajectoryVO;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.service.IUserService;
import org.springblade.modules.system.vo.DeptVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author: Sharry
 * @createTime: 2023/4/26 15:42
 * @version: Version-1.0
 */
@Service
@Slf4j
public class UserTrajectoryServiceImpl extends BaseServiceImpl<UserTrajectoryMapper, UserTrajectory>
	implements IUserTrajectoryService {

	@Autowired
	@Lazy
	private IUserService userService;

	@Autowired
	private BladeRedis bladeRedis;

	@Autowired
	@Lazy
	private BuildingService buildingService;

	@Autowired
	@Lazy
	private IMaintainPlanContentService maintainPlanContentService;

	@Override
	public List<UserTrajectoryQueryVO> getUserList(String tenantId) {
		return baseMapper.getUserList(tenantId);
	}

	/**
	 * 获取 用户轨迹列表
	 *
	 * @param userTrajectorySearchDTO 查询条件DTO
	 * @return 用户轨迹列表
	 */
	@Override
	public List<UserTrajectoryVO> getUserTrajectoryVOList(UserTrajectorySearchDTO userTrajectorySearchDTO) {
		Date endTime = userTrajectorySearchDTO.getEndTime();
		// 时间 加一天
		Calendar c = new GregorianCalendar();
		c.setTime(endTime);
		c.add(Calendar.DATE, 1);
		Date addTime = c.getTime();
		userTrajectorySearchDTO.setEndTime(addTime);
		return baseMapper.getUserTrajectoryVOList(userTrajectorySearchDTO);
	}

	/**
	 * 用户轨迹统计
	 *
	 * @param userTrajectoryVOList 统计列表
	 * @param current              当前页
	 * @param size                 每页大小
	 * @return 统计结果
	 */
	@Override
	public UserTrajectoryCountVO getUserTrajectoryCount(UserTrajectorySearchDTO userTrajectorySearchDTO,
														List<UserTrajectoryVO> userTrajectoryVOList, Integer current, Integer size) {
		UserTrajectoryCountVO userTrajectoryCountVO = new UserTrajectoryCountVO();
		// 当不为空时
		if (ObjectUtils.isNotEmpty(userTrajectoryVOList)) {

			// 去重统计人数
			List<UserTrajectoryVO> collect = userTrajectoryVOList.stream().filter(distinctByKey(UserTrajectory::getUserId))
				.collect(Collectors.toList());

			// 人员数量
			Integer userNum = collect.size();
			userTrajectoryCountVO.setUserNum(userNum);

			// 天数
			Date beginTime = userTrajectorySearchDTO.getBeginTime();
			Date endTime = userTrajectorySearchDTO.getEndTime();
			long between = DateUtil.between(beginTime, endTime, DateUnit.DAY);
			Integer days = (int) between;
			if (0 == between) {
				days = 1;
			}
			userTrajectoryCountVO.setDays(days);

			// 项目总数，去重
			// 日均项目数 ， 根据每日项目进行去重计算 得出 每日项目数，再做平均计算
			// 初始时间 区间， 由于时间倒排序，从最后时间往前开始推算
			Calendar c = Calendar.getInstance();
			c.setTime(endTime);
			c.add(Calendar.DATE, -1);
			HashMap<Long, Integer> dailyMap = new HashMap<>(10);
			int dailyCount = 0;
			HashMap<Long, Integer> map = new HashMap<>(20);
			for (UserTrajectoryVO userTrajectoryVO : userTrajectoryVOList) {
				// 获取项目Id
				Long buildingId = userTrajectoryVO.getBuildingId();
				if (-1 == buildingId) {
					continue;
				}
				if (!map.containsKey(buildingId)) {
					map.put(buildingId, 1);
				}
				// 当大于初始区间时
				if (userTrajectoryVO.getCreateTime().after(c.getTime())) {
					if (!dailyMap.containsKey(buildingId)) {
						dailyMap.put(buildingId, 1);
						dailyCount += 1;
					}
				} else {
					// 重新指向新的HashMap
					dailyMap = new HashMap<>(10);
					c.add(Calendar.DATE, -1);
					dailyMap.put(buildingId, 1);
					dailyCount += 1;
				}
			}
			Integer buildingNum = map.size();
			userTrajectoryCountVO.setBuildingNum(buildingNum);
			// 日均项目数
			Double dailyBuildings = (double) dailyCount / days;
			userTrajectoryCountVO.setDailyBuildings(dailyBuildings);

			// 项目停留总时长
			int projectMin = (int) userTrajectoryVOList.stream().filter(l -> l.getBuildingId() != -1).count();
			Double buildingTotalHours = (double) projectMin / 60;
			userTrajectoryCountVO.setBuildingTotalHours(buildingTotalHours);

			// 通勤总时长
			int wayMin = (int) userTrajectoryVOList.stream().filter(l -> l.getBuildingId() == -1).count();
			Double wayOnTotalHours = (double) wayMin / 60;
			userTrajectoryCountVO.setWayOnTotalHours(wayOnTotalHours);

			// 日均项目停留时长
			Double dailyWorkHours = buildingTotalHours / days;
			userTrajectoryCountVO.setDailyWorkHours(dailyWorkHours);

			// 日均单项目停留时长
			if (buildingNum == 0) {
				userTrajectoryCountVO.setDailySingleBuildingHours(0D);
			} else {
				Double dailySingleBuildingHours = dailyWorkHours / buildingNum;
				userTrajectoryCountVO.setDailySingleBuildingHours(dailySingleBuildingHours);
			}

			// 手动分页
			List<UserTrajectoryVO> voList = pageList(userTrajectoryVOList, current, size);
			userTrajectoryCountVO.setTotal(userTrajectoryVOList.size());

			// 手动赋值
			voList.forEach(u -> {
				Long userId = u.getUserId();
				List<DeptVO> deptVOList = baseMapper.getDeptListByUserId(userId);
				// 名称列表， 逗号 “,” 拼接
				List<String> deptNameList = new ArrayList<>();
				deptVOList.forEach(d -> {
					// 机构���称 add 入机构名称列表
					deptNameList.add(d.getDeptName());
				});
				// 插入机构名称（冗余）
				String deptNameAll = StringUtil.join(deptNameList, ",");
				u.setDeptNameAll(deptNameAll);
			});

			userTrajectoryCountVO.setUserTrajectoryVOList(voList);

		}

		return userTrajectoryCountVO;
	}

	/**
	 * 去重方法
	 *
	 * @param keyExtractor 去重依据
	 * @param <T>          占位符
	 */
	public <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
		Map<Object, Boolean> seen = new ConcurrentHashMap<>();
		return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
	}

	/**
	 * @param list     分页前的集合
	 * @param pageNum  页码
	 * @param pageSize 页数
	 * @param <T>      占位符
	 * @return 分页后的集合
	 */
	public <T> List<T> pageList(List<T> list, int pageNum, int pageSize) {
		// 计算总页数
		int page = list.size() % pageSize == 0 ? list.size() / pageSize : list.size() / pageSize + 1;
		// 兼容性分页参数错误
		pageNum = pageNum <= 0 ? 1 : pageNum;
		pageNum = pageNum >= page ? page : pageNum;
		// 开始索引
		int begin = 0;
		// 结束索引
		int end = 0;
		if (pageNum != page) {
			begin = (pageNum - 1) * pageSize;
			end = begin + pageSize;
		} else {
			begin = (pageNum - 1) * pageSize;
			end = list.size();
		}
		return list.subList(begin, end);
	}

	@Override
	public List<UserTrajectory> getUserNewTrajectoryList() {
		return baseMapper.getUserNewTrajectoryList();
	}

	@Override
	public Boolean record(UserTrajectoryDTO userTrajectoryDTO) {
		// 0. 必要的参数进行判断
		Long userId = userTrajectoryDTO.getUserId();
		if (Func.isNull(userId)) {
			userId = AuthUtil.getUserId();
		}
		// 当坐标为空
		if (Func.isNull(userTrajectoryDTO.getLongitude()) || Func.isNull(userTrajectoryDTO.getLatitude())) {
			throw new ServiceException("业务异常，用户经纬度坐标必须传入！");
		}
		// 当用户操作类型为空
		if (Func.isNull(userTrajectoryDTO.getOperation())) {
			userTrajectoryDTO.setOperation(UserTrajectoryEnum.OPERATION_NO_OPERATE.getValue());
		}
		// 当工单 id 不为空
		Long planId = userTrajectoryDTO.getPlanId();
		if (!Func.isNull(planId)) {
			MaintainPlanContent planById = maintainPlanContentService.getById(planId);
			if (Func.isNull(planById)) {
				throw new ServiceException("业务异常，工单已删除或不存在！");
			}
			Integer typeStatus = planById.getTypeStatus();
			userTrajectoryDTO.setTypeStatus(typeStatus);
		}

		// 获取 用户真名,机构冗余数据 已交给查询时处理
		User user = userService.getOne(new LambdaQueryWrapper<User>().eq(BaseEntity::getId, userTrajectoryDTO.getUserId()));
		if (Func.isNull(user)) {
			throw new ServiceException("业务异常，该用户不存在！");
		}
		String realName = user.getRealName();
		userTrajectoryDTO.setRealName(realName);
		// 2. 根据传入的 坐标 获取所在项目（建筑）
		String prefix = BuildingCacheEnum.BUILDING_REDIS_LIST.getKey();
		String tenantId = AuthUtil.getTenantId();
		String key = prefix + tenantId;

		Boolean exists = bladeRedis.exists(key);
		if (Boolean.FALSE.equals(exists)) {
			log.info("即将进行缓存刷新：" + key);
			// 获取目标对象列表
			List<BuildingRedisVO> l = buildingService.listForVO();
			bladeRedis.del(key);
			// 执行
			l.forEach(b -> bladeRedis.lPush(key, JSON.toJSONString(b)));
		}
		List<BuildingRedisVO> buildingList = new ArrayList<>();
		// 类型转换
		Object[] redisBuildings = bladeRedis.lRange(key, 0, -1).toArray();
		for (int i = 0; i < redisBuildings.length; i++) {
			JSONArray jsonArray = JSONUtil.parseArray(redisBuildings[i]);
			Object[] objects = jsonArray.toArray();
			for (int j = 0; j < objects.length; j++) {
				JSONObject jsonObject = JSONUtil.parseObj(objects[j]);
				BuildingRedisVO buildingRedisVO = JSONUtil.toBean(jsonObject, BuildingRedisVO.class);
				buildingList.add(buildingRedisVO);
			}
		}
		double distance = 1000;
		double userLongitude = Double.parseDouble(userTrajectoryDTO.getLongitude());
		double userLatitude = Double.parseDouble(userTrajectoryDTO.getLatitude());
		String buildingName = null;
		Long buildingId = null;
		// 遍历计算
		for (BuildingRedisVO b : buildingList) {
			double bLongitude = b.getLongitude().doubleValue();
			double bLatitude = b.getLatitude().doubleValue();
			// 计算
			double bDistance = DistanceUtils.getDistance(userLongitude, userLatitude, bLongitude, bLatitude).doubleValue();
			if (bDistance <= distance) {
				distance = bDistance;
				buildingName = b.getName();
				buildingId = b.getId();
			}
		}
		userTrajectoryDTO.setBuildingId(buildingId);
		userTrajectoryDTO.setBuildingName(buildingName);
		// 3. 执行插入操作并返回
		UserTrajectory userTrajectory = copy(userTrajectoryDTO);
		return save(userTrajectory);
	}

	/**
	 * 单个方法不超 80 行，拆分 手动赋值
	 *
	 * @param userTrajectoryDTO 源
	 * @return 目标实体
	 */
	private UserTrajectory copy(UserTrajectoryDTO userTrajectoryDTO) {
		UserTrajectory userTrajectory = new UserTrajectory();
		// 手动赋值
		userTrajectory.setUserId(userTrajectoryDTO.getUserId());
		userTrajectory.setRealName(userTrajectoryDTO.getRealName());
		userTrajectory.setLongitude(userTrajectoryDTO.getLongitude());
		userTrajectory.setLatitude(userTrajectoryDTO.getLatitude());
		userTrajectory.setDeptIdList(userTrajectoryDTO.getDeptIdList());
		userTrajectory.setDeptNameAll(userTrajectoryDTO.getDeptNameAll());
		userTrajectory.setPlanId(userTrajectoryDTO.getPlanId());
		userTrajectory.setBuildingId(userTrajectoryDTO.getBuildingId());
		userTrajectory.setBuildingName(userTrajectoryDTO.getBuildingName());
		userTrajectory.setOperation(userTrajectoryDTO.getOperation());
		userTrajectory.setRecordType(userTrajectoryDTO.getRecordType());
		userTrajectory.setTypeStatus(userTrajectoryDTO.getTypeStatus());
		userTrajectory.setTenantId(AuthUtil.getTenantId());
		return userTrajectory;
	}

	@Override
	public List<UserTrajectoryVO> calculateUserDailyWorkTime(Date beginTime, Date endTime) {
		// 设置默认工作时间：早8点到晚8点
		LocalTime defaultStartTime = LocalTime.of(8, 0);
		LocalTime defaultEndTime = LocalTime.of(20, 0);

		// 获取工作时间范围（小时）
		LocalTime workStartTime = (beginTime != null)
			? beginTime.toInstant().atZone(ZoneId.systemDefault()).toLocalTime()
			: defaultStartTime;
		LocalTime workEndTime = (endTime != null)
			? endTime.toInstant().atZone(ZoneId.systemDefault()).toLocalTime()
			: defaultEndTime;

		double totalWorkHours = Duration.between(workStartTime, workEndTime).toHours();

		// 如果开始时间为空，使用当天的开始时间
		Date queryBeginTime = beginTime != null ? beginTime :
			Date.from(LocalDateTime.of(LocalDate.now(), defaultStartTime)
				.atZone(ZoneId.systemDefault()).toInstant());

		// 如果结束时间为空，使用当天的结束时间
		Date queryEndTime = endTime != null ? endTime :
			Date.from(LocalDateTime.of(LocalDate.now(), defaultEndTime)
				.atZone(ZoneId.systemDefault()).toInstant());

		// 从数据库获取轨迹数据
		List<UserTrajectoryVO> userTrajectoryVOList = baseMapper.getUserLocationVOList(queryBeginTime, queryEndTime);

		Map<Long, Map<LocalDate, List<UserTrajectoryVO>>> userDailyTrajectories = userTrajectoryVOList.stream()
			.collect(Collectors.groupingBy(UserTrajectoryVO::getUserId,
				Collectors.groupingBy(ut -> ut.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())));

		List<UserTrajectoryVO> result = new ArrayList<>();

		for (Map.Entry<Long, Map<LocalDate, List<UserTrajectoryVO>>> userEntry : userDailyTrajectories.entrySet()) {
			Long userId = userEntry.getKey();
			Map<LocalDate, List<UserTrajectoryVO>> dailyTrajectories = userEntry.getValue();

			double totalOnlineHours = 0;
			int totalDays = dailyTrajectories.size();

			for (List<UserTrajectoryVO> dailyTrajectory : dailyTrajectories.values()) {
				totalOnlineHours += calculateDailyOnlineHours(dailyTrajectory, workStartTime, workEndTime);
			}

			double averageOnlineHours = totalOnlineHours / totalDays;
			double averageOfflineHours = totalWorkHours - averageOnlineHours;

			UserTrajectoryVO userTrajectoryVO = dailyTrajectories.values().iterator().next().get(0);
			userTrajectoryVO.setAverageOnlineHours(averageOnlineHours);
			userTrajectoryVO.setAverageOfflineHours(averageOfflineHours);

			result.add(userTrajectoryVO);
		}

		return result;
	}

	@Override
	public List<UserTrajectoryVO> calculateUserDayCount(Date beginTime, Date endTime) {
		List<UserTrajectoryVO> v2 = baseMapper.calculateUserDayCount(beginTime,endTime);
		List<UserTrajectoryVO> result = new ArrayList<>();
		if(Func.isNotEmpty(v2)){
			v2.forEach(e -> {
				List<UserTrajectoryVO> v1 = this.calculateUserDailyWorkTime(DateUtil.parseDateTime(e.getRecordDate()+" 00:00:00"),DateUtil.parseDateTime(e.getRecordDate()+" 23:59:59"));
				UserTrajectoryVO t = v1.stream().filter(k -> k.getUserId().equals(e.getUserId())
					&& DateUtil.format(k.getCreateTime(),"yyyy-MM-dd").equals(e.getRecordDate())).findFirst().orElse(null);
				if(Func.isNotEmpty(t)){
					e.setAverageOnlineHours(t.getAverageOnlineHours());
					e.setAverageOfflineHours(t.getAverageOfflineHours());
				}
			});
			result = v2.stream().sorted(Comparator.comparing(UserTrajectoryVO::getRecordDate).reversed()).collect(Collectors.toList());
		}
		return result;
	}

	@Override
	public void downExcelDetail(Date beginTime, Date endTime, List<List<String>> rows) {
		List<UserTrajectoryVO> list = this.calculateUserDayCount(beginTime,endTime);
		list.forEach(e -> {
			List<String> row = new ArrayList<>(11);
			row.add(Convert.toStr(e.getUserId()));
			row.add(Convert.toStr(e.getRealName()));
			if(Func.isNotEmpty(e.getFromType())){
				if(e.getFromType() == 1){
					row.add("IOS");
				}else if(e.getFromType() == 2){
					row.add("Android");
				}else {
					row.add("小程序");
				}
			}else {
				row.add("-");
			}
			row.add(Convert.toStr(e.getRecordDate()));
			row.add(Convert.toStr(e.getAverageOnlineHours()));
			row.add(Convert.toStr(e.getAppCount()));
			row.add(Convert.toStr(e.getWxCount()));
			row.add(DateUtil.format(e.getMinCreateTime(),"yyyy-MM-dd HH:mm:ss"));
			row.add(DateUtil.format(e.getMaxCreateTime(),"yyyy-MM-dd HH:mm:ss"));
			row.add(Convert.toStr(e.getAppPlanCount()));
			row.add(Convert.toStr(e.getWxPlanCount()));
			rows.add(row);
		});
	}

	private double calculateDailyOnlineHours(List<UserTrajectoryVO> dailyTrajectory, LocalTime workStartTime, LocalTime workEndTime) {
		Set<Integer> onlineIntervals = new HashSet<>();
		int startHour = workStartTime.getHour();
		int endHour = workEndTime.getHour();
		int totalIntervals = (endHour - startHour) * 4; // 每小时4个15分钟间隔

		for (UserTrajectoryVO trajectory : dailyTrajectory) {
			LocalDateTime trajectoryTime = trajectory.getCreateTime().toInstant()
				.atZone(ZoneId.systemDefault()).toLocalDateTime();
			LocalTime trajectoryLocalTime = trajectoryTime.toLocalTime();

			if (trajectoryLocalTime.isAfter(workStartTime) && trajectoryLocalTime.isBefore(workEndTime)) {
				// 计算当前时间在工作时间内的第几个15分钟间隔
				int interval = (trajectoryLocalTime.getHour() - startHour) * 4
					+ trajectoryLocalTime.getMinute() / 15;

				if (interval >= 0 && interval < totalIntervals) {
					onlineIntervals.add(interval);
				}
			}
		}

		return onlineIntervals.size() * 0.25; // 每个区间15分钟，转换为小时
	}



}
